/* common.c : dgnsc common
 * Copyright (C) 2009-2010 drangon <drangon.zhou@gmail.com>
 * 2009-05
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <dgnsc/common.h>

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#ifdef _WIN32
#include <windows.h>
#include <fcntl.h>
#include <io.h>
#else
#include <sys/time.h>
#include <syslog.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdarg.h>
#include <pthread.h>
#endif


/////////////////////////////////////////////
////	<1> lib init/fini

static const char * s_svn_Rev = "$Rev: 58 $";
static const char * s_svn_URL = "$URL: http://libdgnsc.googlecode.com/svn/tags/REL_0_5_0/dgnsc/common.c $";
static char s_version[64] = {0};

static struct {
	char logname[512];
	dgn_mutex_t mutex;
	int fd;
	int year;
	int month;
	int day;
} s_log = { "", NULL, -1, 0, 0, 0 };

const char * dgn_version()
{
	if( s_version[0] == '\0' ) {
		int rev = -1;
		const char * p = NULL;
		p = strstr( s_svn_Rev, "Rev:" );
		if( p != NULL )
			rev = atoi( p + 4 );
		p = strstr( s_svn_URL, "/trunk" );
		if( p != NULL ) {
			p = "trunk";
		}
		else {
			p = strstr( s_svn_URL, "/tags/" );
			if( p != NULL )
				p = p + 6;
			else
				p = "unknown";
		}
	
		int len;
		const char * q = strchr( p, '/' );
		if( q != NULL )
			len = q - p;
		else
			len = (int)strlen( p );
		if( len > 48 )
			len = 48;
		// buffer length is enough
		int ret = snprintf( s_version, sizeof(s_version), "%d ", rev );
		memcpy( s_version + ret, p, len );
		s_version[ ret + len ] = '\0';
	}

	return s_version;
}

static int check_log_file( int year, int month, int day )
{
	if( s_log.fd >= 0 && year == s_log.year && month == s_log.month && day == s_log.day )
		return 0;

	dgn_mutex_lock( s_log.mutex );
	if( s_log.fd >= 0 && year == s_log.year && month == s_log.month && day == s_log.day ) {
		dgn_mutex_unlock( s_log.mutex );
		return 0;
	}

	if( s_log.fd >= 0 ) {
		close( s_log.fd ), s_log.fd = -1;
	}

	// TODO : check dir exist
	char fname[512];
	snprintf( fname, sizeof(fname) - 1, "%s-%04d-%02d-%02d", s_log.logname, year, month, day );
#ifdef _WIN32
	int flag = _O_CREAT | _O_APPEND | _O_WRONLY | _O_BINARY;
#else
	int flag = O_CREAT | O_APPEND | O_WRONLY;
#endif
	int fd = open( fname, flag, 0664 );
	if( fd < 0 ) {
		dgn_mutex_unlock( s_log.mutex );
		return -1;
	}

	s_log.fd = fd;
	s_log.year = year;
	s_log.month = month;
	s_log.day = day;

	dgn_mutex_unlock( s_log.mutex );

	return 1;
}

int dgn_init( const char * log_file_name )
{
	// init log file
	if( log_file_name != NULL && log_file_name[0] != '\0' ) {
		strncpy( s_log.logname, log_file_name, sizeof(s_log.logname) - 1 );
		s_log.logname[ sizeof(s_log.logname) - 1 ] = '\0';
		s_log.mutex = dgn_mutex_init();
		int64_t now = dgn_time_now();
		int year, month, day;
		dgn_time_to( now, &year, &month, &day, NULL, NULL, NULL, NULL );
		check_log_file( year, month, day );
	}

	// init socket
#ifdef _WIN32
	WORD version_requested = MAKEWORD( 2, 2 );
	WSADATA wsa_data;
	WSAStartup( version_requested, &wsa_data );
#else
	signal( SIGPIPE, SIG_IGN );
#endif	

	return 0;
}

int dgn_fini()
{
	// fini log file
	s_log.logname[0] = '\0';
	dgn_mutex_fini( s_log.mutex ), s_log.mutex = NULL;
	if( s_log.fd >= 0 )
		close( s_log.fd ), s_log.fd = -1;
	
	// fini socket
#ifdef _WIN32
	WSACleanup();
#endif
	return 0;
}

/////////////////////////////////////////////
////	<2> assert and log

void dgn_print_log( const char * file, int line, const char * fmt, ... )
{
	if( s_log.logname[0] == '\0' )
		return;

	// TODO : get time improve
	int64_t now = dgn_time_now();
	int year, month, day, hour, min, sec, usec;
	dgn_time_to( now, &year, &month, &day, &hour, &min, &sec, &usec );
	if( check_log_file( year, month, day ) < 0 )
		return;
	
	const char * sf = file;
	const char * p;
	for( p = file; *p != '\0'; p++ ) {
		if( *p == '/' || *p == '\\' )
			sf = p + 1;
	}
	
	va_list ap;
	int len;
	char buf[2048];
	len = snprintf( buf, sizeof(buf) - 1, "[%02d%02d %02d:%02d:%02d.%06d][%s:%d] ",
			month, day, hour, min, sec, usec, sf, line );
	va_start( ap, fmt );
	vsnprintf( buf + len, sizeof(buf) - len - 1, fmt, ap );
	va_end( ap );
	buf[sizeof(buf) - 1] = '\0';
	// TODO : calculate len by return value
	write( s_log.fd, buf, strlen(buf) );
	
	return;
}

/////////////////////////////////////////////
////	<3> mutex

struct dgn_mutex_st
{
#ifdef _WIN32
	CRITICAL_SECTION m_lock;
#else
	pthread_mutex_t m_lock;
#endif
};

dgn_mutex_t dgn_mutex_init()
{
	struct dgn_mutex_st * m = (struct dgn_mutex_st *)malloc( sizeof(struct dgn_mutex_st) );
#ifdef _WIN32
	InitializeCriticalSection( &m->m_lock );
#else
	pthread_mutex_init( &m->m_lock, NULL );
#endif
	return (dgn_mutex_t)m;
}

void dgn_mutex_fini( dgn_mutex_t mutex )
{
	struct dgn_mutex_st * m = (struct dgn_mutex_st *)mutex;
	if( m == NULL )
		return;
#ifdef _WIN32
	DeleteCriticalSection( &m->m_lock );
#else
	pthread_mutex_destroy( &m->m_lock );
#endif
	free( m );
	return;
}

int dgn_mutex_lock( dgn_mutex_t mutex )
{
	struct dgn_mutex_st * m = (struct dgn_mutex_st *)mutex;
#ifdef _WIN32
	EnterCriticalSection( &m->m_lock );
#else
	pthread_mutex_lock( &m->m_lock );
#endif
	return 0;
}

int dgn_mutex_unlock( dgn_mutex_t mutex )
{
	struct dgn_mutex_st * m = (struct dgn_mutex_st *)mutex;
#ifdef _WIN32
	LeaveCriticalSection( &m->m_lock );
#else
	pthread_mutex_unlock( &m->m_lock );
#endif
	return 0;
}

/////////////////////////////////////////////
////	<4> time

uint32_t dgn_time_tick()
{
#ifdef _WIN32
	return timeGetTime();
#else
	struct timespec tms;
	clock_gettime( CLOCK_MONOTONIC, &tms );
	return (uint32_t)( (int64_t)tms.tv_sec * 1000 + tms.tv_nsec / 1000000 );
#endif
}

int64_t dgn_time_now() // us
{
#ifdef _WIN32
	ULARGE_INTEGER fti;
	GetSystemTimeAsFileTime( (LPFILETIME)&fti ); // 100 ns
	// remove seconds between 1601-01-01 to 1970-01-01
	return fti.QuadPart / 10 - ( 11644473600LL * 1000000LL ); 
#else
	struct timeval tv;
	gettimeofday( &tv, NULL );
	return (int64_t)tv.tv_sec * 1000000LL + tv.tv_usec;
#endif
}

int64_t dgn_time_from( int year, int month, int day, int hour, int min, int sec, int usec )
{
	struct tm mytm;
	memset( &mytm, 0, sizeof(mytm) );
	mytm.tm_year = year - 1900;
	mytm.tm_mon = month - 1;
	mytm.tm_mday = day;
	mytm.tm_hour = hour;
	mytm.tm_min = min;
	mytm.tm_sec = sec;
	time_t tt = mktime( &mytm );
	return (int64_t)tt * 1000000LL + usec;
}

void dgn_time_to( int64_t ts, int * year, int * month, int * day, int * hour, int * min, int * sec, int * usec )
{
	time_t tt = ts / 1000000LL;
	struct tm * mytm;
#ifdef _WIN32
	mytm = localtime( &tt );
#else
	struct tm tmp_tm;
	localtime_r( &tt, &tmp_tm );
	mytm = &tmp_tm;
#endif
	if( year != NULL )
		*year = mytm->tm_year + 1900;
	if( month != NULL )
		*month = mytm->tm_mon + 1;
	if( day != NULL )
		*day = mytm->tm_mday;
	if( hour != NULL )
		*hour = mytm->tm_hour;
	if( min != NULL )
		*min = mytm->tm_min;
	if( sec != NULL )
		*sec = mytm->tm_sec;
	if( usec != NULL )
		*usec = ts % 1000000LL;
	return;
}

void dgn_sleepms( int ms )
{
#ifdef _WIN32
	Sleep( ms );
#else
	struct timeval tv;
	tv.tv_sec = ms / 1000;
	tv.tv_usec = ( ms % 1000 ) * 1000;
	// TODO : will EINTR affect ?
	select( 0, NULL, NULL, NULL, &tv );
#endif
	return;
}

